package com.cv.platform.service.video;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.cv.framework.common.pojo.PageResult;
import com.cv.framework.common.utils.object.BeanUtils;
import com.cv.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cv.framework.mybatis.core.query.QueryWrapperX;
import com.cv.framework.security.core.utils.SecurityFrameworkUtil;
import com.cv.infra.api.file.FileApi;
import com.cv.infra.api.websocket.WebSocketSenderApi;
import com.cv.platform.controller.audit.video.vo.AuditVideoPageReqVO;
import com.cv.platform.controller.audit.video.vo.AuditVideoSaveReqVO;
import com.cv.platform.controller.platform.collection.vo.collection.CvVideoCollectionDetailPageReqVO;
import com.cv.platform.controller.platform.label.vo.record.CvVideoLabelBatchSaveReqVO;
import com.cv.platform.controller.platform.label.vo.record.CvVideoLabelRespVO;
import com.cv.platform.controller.platform.message.vo.conversation.CvConversationSaveReqVO;
import com.cv.platform.controller.platform.message.vo.record.CvMessageSaveReqVO;
import com.cv.platform.controller.platform.video.vo.CvVideoItemRespVO;
import com.cv.platform.controller.platform.video.vo.CvVideoItemTypeRespVO;
import com.cv.platform.controller.platform.video.vo.detail.CvVideoDetailRespVO;
import com.cv.platform.controller.platform.video.vo.menu.CvVideoMenuReqVO;
import com.cv.platform.controller.platform.video.vo.profile.CvVideoProfileReqVO;
import com.cv.platform.controller.platform.video.vo.profile.CvVideoUncollectedReqVO;
import com.cv.platform.controller.platform.video.vo.profile.CvVideoUncollectedRespVO;
import com.cv.platform.controller.platform.video.vo.video.CvVideoReqVO;
import com.cv.platform.controller.platform.video.vo.video.CvVideoSaveReqVO;
import com.cv.platform.controller.system.dashboard.vo.VideoTypeCountData;
import com.cv.platform.dal.dataobject.video.CvVideoDO;
import com.cv.platform.dal.es.index.video.CvVideoEntity;
import com.cv.platform.dal.mapper.video.CvVideoMapper;
import com.cv.platform.enums.VideoStatusEnum;
import com.cv.platform.enums.YesOrNoEnum;
import com.cv.platform.service.carousel.CvVideoCarouselService;
import com.cv.platform.service.collection.CvVideoCollectionRefService;
import com.cv.platform.service.collection.CvVideoCollectionService;
import com.cv.platform.service.favourite.CvVideoFavouriteService;
import com.cv.platform.service.history.CvVideoHistoryRecordService;
import com.cv.platform.service.label.CvVideoLabelService;
import com.cv.platform.service.message.CvConversationService;
import com.cv.platform.service.message.CvMessageService;
import com.cv.platform.service.report.CvReportService;
import com.cv.platform.service.search.CvVideoSearchService;
import com.cv.system.api.dict.DictDataApi;
import com.cv.system.api.dict.dto.DictDataRespDTO;
import com.cv.system.api.user.UserApi;
import com.cv.system.api.user.dto.UserProfileRespDTO;
import com.huaban.analysis.jieba.JiebaSegmenter;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

import static com.cv.platform.dal.redis.RedisKeyConstants.DAILY_VIDEO_TYPE_UV;

/**
 * 视频业务层接口实现类
 *
 * @author Charles_XDXD
 */
@Service
public class CvVideoServiceImpl implements CvVideoService {

    @Resource
    private CvVideoMapper cvVideoMapper;

    @Resource
    private CvVideoLikeService cvVideoLikeService;

    @Resource
    private CvVideoFavouriteService cvVideoFavouriteService;

    @Resource
    private CvVideoHistoryRecordService cvVideoHistoryRecordService;

    @Resource
    private CvVideoSearchService cvVideoSearchService;

    @Resource
    private CvVideoLabelService cvVideoLabelService;

    @Resource
    @Lazy
    private CvVideoCollectionService cvVideoCollectionService;

    @Resource
    @Lazy
    private CvVideoCollectionRefService cvVideoCollectionRefService;

    @Resource
    private CvConversationService cvConversationService;

    @Resource
    private CvMessageService cvMessageService;

    @Resource
    private CvReportService cvReportService;

    @Resource
    private CvVideoCarouselService cvVideoCarouselService;

    @DubboReference
    private UserApi userApi;

    @DubboReference
    private FileApi fileApi;

    @DubboReference
    private DictDataApi dictDataApi;

    @DubboReference
    private WebSocketSenderApi socketSenderApi;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 具体思路：
     * 1. 判断用户是否登录，如果没有登录，则推荐播放量最多的视频。
     * 2. 如果用户登录，则需要根据用户的历史播放记录、点赞记录、收藏记录推荐类似的视频。
     * 后期可以优化一下代码
     */
    @Override
    public List<CvVideoItemRespVO> getVideoHotRecommend() {
        List<CvVideoItemRespVO> videoHomeRespVOList = new ArrayList<>();

        if (ObjUtil.isEmpty(SecurityFrameworkUtil.getLoginUser())) {
            QueryWrapper<CvVideoDO> wrapper = new QueryWrapper<CvVideoDO>()
                    .select("id, user_id, title, view_count, thumbnail, create_time")
                    .eq("is_visible", YesOrNoEnum.YES.getCode())
                    .eq("status", VideoStatusEnum.PASS.getStatus())
                    .eq("deleted", YesOrNoEnum.NO.getCode())
                    .orderByDesc("view_count")
                    .last("limit 5");

            List<CvVideoDO> cvVideoDOList = cvVideoMapper.selectList(wrapper);
            if (CollUtil.isNotEmpty(cvVideoDOList)) {
                cvVideoDOList.forEach(cvVideoDO -> {
                    videoHomeRespVOList.add(packageVideoHomeRespVO(cvVideoDO));
                });
            }
        } else {
            /*
             * 基于用户点赞，收藏，播放记录来推荐视频
             * 权重：点赞 >= 收藏 > 播放记录
             * 细分权重：标题 > 作者昵称 > 标签 > 类型
             * 分词工具：jieba
             * 计算公式：余弦相似度
             */
            Long userId = SecurityFrameworkUtil.getLoginUserId();

            // 获取播放量最高的视频倒序
            List<CvVideoEntity> cvVideoHotList = cvVideoSearchService.getAllVideoOrderByViewCount();

            // 截取前5个视频
            List<CvVideoEntity> top5Videos = cvVideoHotList.size() > 5 ? cvVideoHotList.subList(0, 5) : cvVideoHotList;

            // 用户点赞的视频数据
            List<CvVideoEntity> cvVideoLikeList = cvVideoLikeService.getAllVideoLikeByUserId(userId);

            // 用户收藏的视频数据
            List<CvVideoEntity> cvVideoFavouriteList = cvVideoFavouriteService.getAllVideoFavouriteByUserId(userId);

            // 用户的视频播放记录数据
            List<CvVideoEntity> cvVideoHistoryRecordList = cvVideoHistoryRecordService.getAllVideoHistoryRecordByUserId(userId);

            // 记录已添加的视频编号
            Set<Long> recordVideoIdSet = new HashSet<>();

            if (CollUtil.isNotEmpty(top5Videos) && CollUtil.isNotEmpty(cvVideoLikeList)) {
                for (CvVideoEntity cvVideo : top5Videos) {
                    if (videoHomeRespVOList.size() >= 5) {
                        return videoHomeRespVOList;
                    }

                    for (CvVideoEntity cvVideoEntity : cvVideoLikeList) {
                        if (recordVideoIdSet.contains(cvVideoEntity.getId())) {
                            continue;
                        }

                        double similarity = cosineSimilarity(getTextVector(cvVideo.getTitle()), getTextVector(cvVideoEntity.getTitle()));
                        similarity += cosineSimilarity(getTextVector(cvVideo.getNickname()), getTextVector(cvVideoEntity.getNickname())) * 0.8;
                        if (CollUtil.isNotEmpty(cvVideo.getLabels()) || CollUtil.isNotEmpty(cvVideoEntity.getLabels())) {
                            similarity += cosineSimilarity(getTextVector(cvVideo.getLabels().toString()), getTextVector(cvVideoEntity.getLabels().toString())) * 0.6;
                        }
                        similarity += cosineSimilarity(getTextVector(cvVideo.getType()), getTextVector(cvVideoEntity.getType())) * 0.4;

                        if (similarity >= 0.8) {
                            videoHomeRespVOList.add(packageVideoHomeRespVO(cvVideoEntity));
                            recordVideoIdSet.add(cvVideoEntity.getId());
                        }
                    }
                }
            }

            if (CollUtil.isNotEmpty(top5Videos) && CollUtil.isNotEmpty(cvVideoFavouriteList)) {
                for (CvVideoEntity cvVideo : top5Videos) {
                    if (videoHomeRespVOList.size() >= 5) {
                        return videoHomeRespVOList;
                    }

                    for (CvVideoEntity cvVideoEntity : cvVideoFavouriteList) {
                        if (recordVideoIdSet.contains(cvVideoEntity.getId())) {
                            continue;
                        }

                        double similarity = cosineSimilarity(getTextVector(cvVideo.getTitle()), getTextVector(cvVideoEntity.getTitle()));
                        similarity += cosineSimilarity(getTextVector(cvVideo.getNickname()), getTextVector(cvVideoEntity.getNickname())) * 0.8;
                        if (CollUtil.isNotEmpty(cvVideo.getLabels()) || CollUtil.isNotEmpty(cvVideoEntity.getLabels())) {
                            similarity += cosineSimilarity(getTextVector(cvVideo.getLabels().toString()), getTextVector(cvVideoEntity.getLabels().toString())) * 0.6;
                        }
                        similarity += cosineSimilarity(getTextVector(cvVideo.getType()), getTextVector(cvVideoEntity.getType())) * 0.4;

                        if (similarity >= 0.8) {
                            videoHomeRespVOList.add(packageVideoHomeRespVO(cvVideoEntity));
                            recordVideoIdSet.add(cvVideoEntity.getId());
                        }
                    }
                }
            }

            if (CollUtil.isNotEmpty(top5Videos) && CollUtil.isNotEmpty(cvVideoHistoryRecordList)) {
                for (CvVideoEntity cvVideo : top5Videos) {
                    if (videoHomeRespVOList.size() >= 5) {
                        return videoHomeRespVOList;
                    }

                    for (CvVideoEntity cvVideoEntity : cvVideoHistoryRecordList) {
                        if (recordVideoIdSet.contains(cvVideoEntity.getId())) {
                            continue;
                        }

                        double similarity = cosineSimilarity(getTextVector(cvVideo.getTitle()), getTextVector(cvVideoEntity.getTitle()));
                        similarity += cosineSimilarity(getTextVector(cvVideo.getNickname()), getTextVector(cvVideoEntity.getNickname())) * 0.8;
                        if (CollUtil.isNotEmpty(cvVideo.getLabels()) || CollUtil.isNotEmpty(cvVideoEntity.getLabels())) {
                            similarity += cosineSimilarity(getTextVector(cvVideo.getLabels().toString()), getTextVector(cvVideoEntity.getLabels().toString())) * 0.6;
                        }
                        similarity += cosineSimilarity(getTextVector(cvVideo.getType()), getTextVector(cvVideoEntity.getType())) * 0.4;

                        if (similarity >= 0.8) {
                            videoHomeRespVOList.add(packageVideoHomeRespVO(cvVideoEntity));
                            recordVideoIdSet.add(cvVideoEntity.getId());
                        }
                    }
                }
            }

            if (videoHomeRespVOList.size() < 5) {
                for (CvVideoEntity cvVideo : top5Videos) {
                    if (videoHomeRespVOList.size() >= 5) {
                        return videoHomeRespVOList;
                    }

                    if (!recordVideoIdSet.contains(cvVideo.getId())) {
                        videoHomeRespVOList.add(packageVideoHomeRespVO(cvVideo));
                    }
                }
            }
        }

        return videoHomeRespVOList;
    }

    @Override
    public List<CvVideoItemTypeRespVO> getVideoTypeHotRecommend() {
        Collection<DictDataRespDTO> cvVideoTypes = dictDataApi.getDictDataListByType("cv_video_types");

        if (CollUtil.isEmpty(cvVideoTypes)) {
            return Collections.emptyList();
        }

        List<CvVideoItemTypeRespVO> videoTypeHotRecommendList = new ArrayList<>();
        for (DictDataRespDTO cvVideoType : cvVideoTypes) {
            String type = cvVideoType.getLabel();
            String typeValue = cvVideoType.getValue();
            QueryWrapper<CvVideoDO> wrapper = new QueryWrapper<CvVideoDO>()
                    .select("id, user_id, title, view_count, thumbnail, create_time")
                    .eq("type", typeValue)
                    .eq("is_visible", YesOrNoEnum.YES.getCode())
                    .eq("status", VideoStatusEnum.PASS.getStatus())
                    .eq("deleted", YesOrNoEnum.NO.getCode())
                    .orderByDesc("view_count")
                    .last("limit 5");

            List<CvVideoDO> cvVideoDOList = cvVideoMapper.selectList(wrapper);
            List<CvVideoItemRespVO> cvVideoItemRespVOList = new ArrayList<>();
            if (CollUtil.isNotEmpty(cvVideoDOList)) {
                cvVideoDOList.forEach(cvVideoDO -> {
                    cvVideoItemRespVOList.add(packageVideoHomeRespVO(cvVideoDO));
                });
            }

            videoTypeHotRecommendList.add(CvVideoItemTypeRespVO.builder()
                    .type(type)
                    .typeValue(typeValue)
                    .cvVideoItemRespVOList(cvVideoItemRespVOList)
                    .build());
        }

        return videoTypeHotRecommendList;
    }

    @Override
    public List<CvVideoItemRespVO> getAllAllowedVideo() {
        QueryWrapper<CvVideoDO> wrapper = new QueryWrapper<CvVideoDO>()
                .select("id, user_id, title, view_count, type, thumbnail, create_time")
                .eq("is_visible", YesOrNoEnum.YES.getCode())
                .eq("status", VideoStatusEnum.PASS.getStatus())
                .eq("deleted", YesOrNoEnum.NO.getCode())
                .orderByDesc("view_count");

        return cvVideoMapper.selectList(wrapper).stream().map(this::packageVideoHomeRespVO).collect(Collectors.toList());
    }

    @Override
    public List<CvVideoItemRespVO> getRecommendVideoList(Long videoId) {
        if (ObjUtil.isEmpty(videoId)) {
            return Collections.emptyList();
        }

        QueryWrapperX<CvVideoDO> wrapper = new QueryWrapperX<CvVideoDO>()
                .eq("is_visible", YesOrNoEnum.YES.getCode())
                .eq("status", VideoStatusEnum.PASS.getStatus())
                .eq("deleted", YesOrNoEnum.NO.getCode())
                .eq("id", videoId);

        CvVideoDO cvVideoDO = cvVideoMapper.selectOne(wrapper);

        List<CvVideoEntity> allVideoOrderByViewCount = cvVideoSearchService.getAllVideoOrderByViewCount();
        if (ObjUtil.isEmpty(cvVideoDO) || CollUtil.isEmpty(allVideoOrderByViewCount)) {
            return Collections.emptyList();
        }

        CvVideoEntity cvVideo = BeanUtils.toBean(cvVideoDO, CvVideoEntity.class);
        cvVideo.setNickname(userApi.getNicknameById(cvVideoDO.getUserId()));
        cvVideo.setLabels(cvVideoLabelService.getAllLabelByVideoId(cvVideoDO.getId()).stream().map(CvVideoLabelRespVO::getLabelName).toList());

        List<CvVideoItemRespVO> respVOList = new ArrayList<>();
        for (CvVideoEntity cvVideoEntity : allVideoOrderByViewCount) {
            if (respVOList.size() >= 10) {
                return respVOList;
            }

            if (cvVideoEntity.getId().equals(cvVideoDO.getId())) {
                continue;
            }

            double similarity = cosineSimilarity(getTextVector(cvVideo.getTitle()), getTextVector(cvVideoEntity.getTitle()));
            similarity += cosineSimilarity(getTextVector(cvVideo.getNickname()), getTextVector(cvVideoEntity.getNickname())) * 0.8;
            if (CollUtil.isNotEmpty(cvVideo.getLabels()) || CollUtil.isNotEmpty(cvVideoEntity.getLabels())) {
                similarity += cosineSimilarity(getTextVector(cvVideo.getLabels().toString()), getTextVector(cvVideoEntity.getLabels().toString())) * 0.6;
            }
            similarity += cosineSimilarity(getTextVector(cvVideo.getType()), getTextVector(cvVideoEntity.getType())) * 0.4;

            if (similarity >= 0.8) {
                respVOList.add(packageVideoHomeRespVO(cvVideoEntity));
            }
        }

        return respVOList;
    }

    @Override
    public CvVideoDO getVideoById(Long videoId) {
        QueryWrapperX<CvVideoDO> wrapper = new QueryWrapperX<CvVideoDO>()
                .eq("is_visible", YesOrNoEnum.YES.getCode())
                .eq("status", VideoStatusEnum.PASS.getStatus())
                .eq("deleted", YesOrNoEnum.NO.getCode())
                .eq("id", videoId);

        return cvVideoMapper.selectOne(wrapper);
    }

    @Override
    public void addVideoViewCount(Long videoId) {
        if (ObjUtil.isNotEmpty(videoId)) {
            UpdateWrapper<CvVideoDO> wrapper = new UpdateWrapper<CvVideoDO>()
                    .setSql("view_count = view_count + 1")
                    .eq("id", videoId);
            cvVideoMapper.update(null, wrapper);
        }
    }

    @Override
    public void addVideoCommentCount(Long videoId) {
        if (ObjUtil.isNotEmpty(videoId)) {
            UpdateWrapper<CvVideoDO> wrapper = new UpdateWrapper<CvVideoDO>()
                    .setSql("comment_count = comment_count + 1")
                    .eq("id", videoId);
            cvVideoMapper.update(null, wrapper);
        }
    }

    @Override
    public CvVideoDetailRespVO getVideoDetail(Long videoId, boolean isAudit) {
        if (ObjUtil.isEmpty(videoId)) {
            return null;
        }

        CvVideoDO cvVideoDO = cvVideoMapper.selectById(videoId);
        if (!isAudit) {
            if (ObjUtil.isEmpty(cvVideoDO)
                    || cvVideoDO.getIsVisible().equals(YesOrNoEnum.NO.getCode())
                    || !cvVideoDO.getStatus().equals(VideoStatusEnum.PASS.getStatus())) {
                return null;
            }

            // 记录 uv
            String key = String.format(DAILY_VIDEO_TYPE_UV, LocalDate.now(), cvVideoDO.getType());
            Long uv = stringRedisTemplate.opsForValue().get(key) == null ? 1L : Integer.parseInt(stringRedisTemplate.opsForValue().get(key)) + 1;
            stringRedisTemplate.opsForValue().set(key, String.valueOf(uv));
        } else {
            if (ObjUtil.isEmpty(cvVideoDO)) {
                return null;
            }
        }

        CvVideoDetailRespVO respVO = BeanUtils.toBean(cvVideoDO, CvVideoDetailRespVO.class);
        respVO.setPublishTime(cvVideoDO.getCreateTime());

        if (ObjUtil.isNotEmpty(cvVideoDO.getThumbnail())) {
            respVO.setThumbnailUrl(fileApi.getUrl(cvVideoDO.getThumbnail()));
        }

        if (ObjUtil.isNotEmpty(cvVideoDO.getUrl())) {
            String videoPath = fileApi.getUrl(cvVideoDO.getUrl());
            String videoName = videoPath.substring(videoPath.lastIndexOf("/") + 1);
            respVO.setResource720p(videoPath + "/" + videoName + "_720p.m3u8");
            respVO.setResource1080p(videoPath + "/" + videoName + "_1080p.m3u8");
        }

        if (ObjUtil.isNotEmpty(cvVideoDO.getUserId())) {
            UserProfileRespDTO profile = userApi.getProfileById(cvVideoDO.getUserId());
            respVO.setNickname(profile.getNickname());
            respVO.setAvatar(profile.getAvatar());
            respVO.setAvatarUrl(profile.getAvatarUrl());
            respVO.setSignature(profile.getSignature());
        }

        respVO.setLabelList(cvVideoLabelService.getAllLabelByVideoId(videoId));
        return respVO;
    }

    @Override
    public PageResult<CvVideoItemRespVO> getVideoPage(CvVideoMenuReqVO reqVO) {
        PageResult<CvVideoItemRespVO> pageResult = cvVideoMapper.selectPage(reqVO);

        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        pageResult.getList().forEach(item -> {
            item.setNickname(userApi.getNicknameById(item.getUserId()));
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        });

        return pageResult;
    }

    @Override
    public PageResult<CvVideoItemRespVO> getUserVideoPage(CvVideoProfileReqVO reqVO) {
        PageResult<CvVideoItemRespVO> pageResult = cvVideoMapper.selectPage(reqVO);

        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        pageResult.getList().forEach(item -> {
            item.setNickname(userApi.getNicknameById(item.getUserId()));
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        });

        return pageResult;
    }

    @Override
    public PageResult<CvVideoItemRespVO> getCollectionVideoPage(CvVideoCollectionDetailPageReqVO reqVO, List<Long> videoIdList) {
        if (CollUtil.isEmpty(videoIdList)) {
            return PageResult.empty();
        }

        PageResult<CvVideoItemRespVO> pageResult = cvVideoMapper.selectPage(reqVO, videoIdList);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        pageResult.getList().forEach(item -> {
            item.setNickname(userApi.getNicknameById(item.getUserId()));
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        });

        return pageResult;
    }

    @Override
    public PageResult<CvVideoUncollectedRespVO> getUncollectedVideoPage(CvVideoUncollectedReqVO reqVO) {
        List<Long> videoIdList = cvVideoCollectionService.getVideoIdList();
        PageResult<CvVideoUncollectedRespVO> pageResult = cvVideoMapper.selectPage(reqVO, videoIdList);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        pageResult.getList().forEach(item -> {
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        });

        return pageResult;
    }

    @Override
    public PageResult<CvVideoUncollectedRespVO> getUncollectedAndCurrent(CvVideoUncollectedReqVO reqVO) {
        List<Long> videoIdList = cvVideoCollectionService.getVideoIdList();
        List<Long> selectedVideoList = new ArrayList<>();
        if (ObjUtil.isNotEmpty(reqVO.getCollectionId())) {
            selectedVideoList = cvVideoCollectionRefService.getVideoIdListByCollectionId(reqVO.getCollectionId());
        }

        videoIdList.removeAll(selectedVideoList);
        PageResult<CvVideoUncollectedRespVO> pageResult = cvVideoMapper.selectPage(reqVO, videoIdList);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        for (CvVideoUncollectedRespVO item : pageResult.getList()) {
            item.setSelected(selectedVideoList.contains(item.getId()));
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        }

        return pageResult;
    }


    @Override
    public Long saveVideo(CvVideoSaveReqVO reqVO) {
        CvVideoDO cvVideoDO = BeanUtils.toBean(reqVO, CvVideoDO.class);
        cvVideoDO.setUserId(SecurityFrameworkUtil.getLoginUserId());

        // 设置视频状态：转码中/待审核
        if (ObjUtil.isEmpty(reqVO.getId())) {
            if (fileApi.isTranscoded(cvVideoDO.getUrl())) {
                cvVideoDO.setStatus(VideoStatusEnum.AUDITING.getStatus());
            } else {
                cvVideoDO.setStatus(VideoStatusEnum.TRANSCODING.getStatus());
            }
        }
        cvVideoMapper.insertOrUpdate(cvVideoDO);

        if (CollUtil.isNotEmpty(reqVO.getLabelList())) {
            cvVideoLabelService.batchInsertOrUpdateLabel(
                    new CvVideoLabelBatchSaveReqVO()
                            .setVideoId(cvVideoDO.getId())
                            .setLabels(reqVO.getLabelList())
            );
        }

        if (cvVideoDO.getIsVisible().equals(YesOrNoEnum.YES.getCode())
                && cvVideoDO.getStatus().equals(VideoStatusEnum.PASS.getStatus())) {
            cvVideoSearchService.insertOrUpdate(cvVideoDO);
        } else {
            cvVideoSearchService.delete(cvVideoDO.getId());
        }

        return cvVideoDO.getId();
    }

    @Override
    public void auditVideo(AuditVideoSaveReqVO reqVO) {
        Long videoId = reqVO.getId();
        String status = reqVO.getStatus();

        if (ObjUtil.isEmpty(videoId) || StrUtil.isEmpty(status)) {
            return;
        }

        CvVideoDO video = cvVideoMapper.selectById(videoId);
        if (ObjUtil.isNotEmpty(video) && !video.getStatus().equals(status)) {
            video.setStatus(status);
            cvVideoMapper.updateById(video);

            Long conversationId = cvConversationService.getConversationId(SecurityFrameworkUtil.getLoginUserId(), video.getUserId());
            if (ObjUtil.isEmpty(conversationId)) {
                conversationId = cvConversationService.saveConversation(new CvConversationSaveReqVO()
                        .setTargetUserId(video.getUserId())
                );
            }
            if (VideoStatusEnum.PASS.getStatus().equals(status)) {
                if (video.getIsVisible().equals(YesOrNoEnum.YES.getCode())) {
                    cvVideoSearchService.insertOrUpdate(video);
                }
                cvMessageService.saveMessage(new CvMessageSaveReqVO()
                        .setContent("视频审核通过，快去查看吧！")
                        .setConversationId(conversationId)
                        .setReceiverId(video.getUserId())
                        .setSenderId(SecurityFrameworkUtil.getLoginUserId()));
            } else {
                cvVideoSearchService.delete(videoId);
                cvMessageService.saveMessage(new CvMessageSaveReqVO()
                        .setContent("您的视频《" + video.getTitle() + "》因内容不符合平台规范，审核未通过/已被下架。")
                        .setConversationId(conversationId)
                        .setReceiverId(video.getUserId())
                        .setSenderId(SecurityFrameworkUtil.getLoginUserId()));
                cvReportService.deleteAllReport(videoId);
            }
        }
    }

    @Override
    public void deleteVideo(Long videoId) {
        if (ObjUtil.isNotEmpty(videoId)) {
            cvVideoMapper.deleteById(videoId);
            cvVideoSearchService.delete(videoId);
        }
    }

    @Override
    public void deleteAllVideo(Long userId) {
        if (ObjUtil.isNotEmpty(userId)) {
            LambdaQueryWrapperX<CvVideoDO> wrapper = new LambdaQueryWrapperX<CvVideoDO>()
                    .eq(CvVideoDO::getUserId, userId);
            List<CvVideoDO> cvVideoDOList = cvVideoMapper.selectList(wrapper);

            if (CollUtil.isNotEmpty(cvVideoDOList)) {
                cvVideoDOList.forEach(item -> {
                    deleteVideo(item.getId());
                    cvVideoCarouselService.deleteCarouselByVideoId(item.getId());
                });
            }
        }
    }

    @Override
    public PageResult<CvVideoItemRespVO> getVideoOperationPage(CvVideoReqVO reqVO) {
        PageResult<CvVideoItemRespVO> pageResult = cvVideoMapper.selectPage(reqVO);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        pageResult.getList().forEach(item -> {
            item.setNickname(userApi.getNicknameById(item.getUserId()));
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        });
        return pageResult;
    }

    @Override
    public CvVideoDetailRespVO getVideoOperationDetail(Long videoId) {
        if (ObjUtil.isEmpty(videoId)) {
            return null;
        }

        CvVideoDO cvVideoDO = cvVideoMapper.selectById(videoId);
        if (ObjUtil.isEmpty(cvVideoDO)) {
            return null;
        }

        CvVideoDetailRespVO respVO = BeanUtils.toBean(cvVideoDO, CvVideoDetailRespVO.class);
        respVO.setPublishTime(cvVideoDO.getCreateTime());

        if (ObjUtil.isNotEmpty(cvVideoDO.getThumbnail())) {
            respVO.setThumbnailUrl(fileApi.getUrl(cvVideoDO.getThumbnail()));
        }

        respVO.setLabelList(cvVideoLabelService.getAllLabelByVideoId(videoId));
        return respVO;
    }

    @Override
    public Integer getVideoPV() {
        LambdaQueryWrapper<CvVideoDO> wrapper = new LambdaQueryWrapperX<CvVideoDO>()
                .select(CvVideoDO::getViewCount);
        return cvVideoMapper.selectList(wrapper).stream().mapToInt(CvVideoDO::getViewCount).sum();
    }

    @Override
    public List<VideoTypeCountData> getVideoTypeCountData() {
        Collection<DictDataRespDTO> cvVideoTypes = dictDataApi.getDictDataListByType("cv_video_types");
        if (CollUtil.isEmpty(cvVideoTypes)) {
            return Collections.emptyList();
        }

        List<String> typeNameList = cvVideoTypes.stream().map(DictDataRespDTO::getLabel).toList();
        List<String> typeList = cvVideoTypes.stream().map(DictDataRespDTO::getValue).toList();

        // 遍历类型查询对应视频数量
        List<VideoTypeCountData> result = new ArrayList<>();
        for (int i = 0; i < typeList.size(); i++) {
            String type = typeList.get(i);
            String typeName = typeNameList.get(i);

            // 查询该类型视频数量
            Long count = cvVideoMapper.selectCount(new LambdaQueryWrapperX<CvVideoDO>()
                    .eq(CvVideoDO::getType, type)
            );

            result.add(new VideoTypeCountData(typeName, count.intValue()));
        }

        return result;
    }

    @Override
    public PageResult<CvVideoItemRespVO> getVideoAuditPage(AuditVideoPageReqVO reqVO) {
        PageResult<CvVideoItemRespVO> pageResult = cvVideoMapper.selectPage(reqVO);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty();
        }

        pageResult.getList().forEach(item -> {
            item.setNickname(userApi.getNicknameById(item.getUserId()));
            item.setThumbnailUrl(fileApi.getUrl(item.getThumbnail()));
        });
        return pageResult;
    }

    @Override
    public void updateTranscodingToAuditing(Long url) {
        LambdaUpdateWrapper<CvVideoDO> wrapper = new LambdaUpdateWrapper<CvVideoDO>()
                .set(CvVideoDO::getStatus, VideoStatusEnum.AUDITING.getStatus())
                .eq(CvVideoDO::getUrl, url);

        cvVideoMapper.update(wrapper);
    }

    // ================================================ 私有方法 ================================================

    /**
     * 计算余弦相似度
     */
    private double cosineSimilarity(Map<String, Integer> vector1, Map<String, Integer> vector2) {
        if (MapUtil.isEmpty(vector1) || MapUtil.isEmpty(vector2)) {
            return 0.0;
        }

        Set<String> allTerms = new HashSet<>(vector1.keySet());
        allTerms.addAll(vector2.keySet());

        double dotProduct = 0.0;
        double magnitudeA = 0.0;
        double magnitudeB = 0.0;

        for (String term : allTerms) {
            int countA = vector1.getOrDefault(term, 0);
            int countB = vector2.getOrDefault(term, 0);
            dotProduct += countA * countB;
            magnitudeA += countA * countA;
            magnitudeB += countB * countB;
        }

        double denominator = Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB);
        return denominator == 0.0 ? 0.0 : dotProduct / denominator;
    }

    /**
     * 通过 jieba 分词工具计算文本向量
     */
    private Map<String, Integer> getTextVector(String text) {
        if (StrUtil.isEmpty(text)) {
            return new HashMap<>();
        }

        JiebaSegmenter segmenter = new JiebaSegmenter();
        List<String> words = segmenter.sentenceProcess(text);
        Map<String, Integer> wordCount = new HashMap<>();
        for (String word : words) {
            wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
        }
        return wordCount;
    }

    /**
     * 封装视频推荐对象
     */
    private CvVideoItemRespVO packageVideoHomeRespVO(CvVideoDO cvVideoDO) {
        return CvVideoItemRespVO.builder()
                .id(cvVideoDO.getId())
                .userId(cvVideoDO.getUserId())
                .nickname(userApi.getNicknameById(cvVideoDO.getUserId()))
                .thumbnail(cvVideoDO.getThumbnail())
                .thumbnailUrl(fileApi.getUrl(cvVideoDO.getThumbnail()))
                .type(cvVideoDO.getType())
                .title(cvVideoDO.getTitle())
                .viewCount(cvVideoDO.getViewCount())
                .publishTime(cvVideoDO.getCreateTime())
                .build();
    }

    /**
     * 封装视频推荐对象
     */
    private CvVideoItemRespVO packageVideoHomeRespVO(CvVideoEntity videoEntity) {
        return CvVideoItemRespVO.builder()
                .id(videoEntity.getId())
                .userId(videoEntity.getUserId())
                .nickname(videoEntity.getNickname())
                .thumbnail(videoEntity.getThumbnail())
                .thumbnailUrl(fileApi.getUrl(videoEntity.getThumbnail()))
                .title(videoEntity.getTitle())
                .type(videoEntity.getType())
                .viewCount(videoEntity.getViewCount())
                .publishTime(videoEntity.getPublishTime())
                .build();
    }

}
